/**
 * $Id: Cell.java,v 1.29 2010/02/03 16:55:39 gaudenz Exp $
 * Copyright (c) 2007, Gaudenz Alder
 */
package com.magnificent.graph.model;

import com.magnificent.panda.model.entity.FlowActor;
import com.magnificent.panda.model.entity.FlowActorType;
import com.magnificent.panda.model.entity.FlowEvent;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * Cells are the elements of the graph model. They represent the state
 * of the groups, vertices and edges in a graph.
 * <p/>
 * <h4>Edge Labels</h4>
 * <p/>
 * Using the x- and y-coordinates of a cell's geometry it is
 * possible to position the label on edges on a specific location
 * on the actual edge shape as it appears on the screen. The
 * x-coordinate of an edge's geometry is used to describe the
 * distance from the center of the edge from -1 to 1 with 0
 * being the center of the edge and the default value. The
 * y-coordinate of an edge's geometry is used to describe
 * the absolute, orthogonal distance in pixels from that
 * point. In addition, the mxGeometry.offset is used
 * as a absolute offset vector from the resulting point.
 * <p/>
 * The width and height of an edge geometry are ignored.
 * <p/>
 * To add more than one edge label, add a child vertex with
 * a relative geometry. The x- and y-coordinates of that
 * geometry will have the same semantiv as the above for
 * edge labels.
 */
public class Cell implements mxICell, Cloneable, Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 910211337632342672L;

    /**
     * Holds the Id. Default is null.
     */
    protected String id;


    /**
     * Role of cell.
     */
    protected CellRole role;

    /**
     * Holds the user object. Default is null.
     */
    protected Object value;

    /**
     * Holds the geometry. Default is null.
     */
    protected mxGeometry geometry;

    /**
     * Holds the style as a string of the form
     * stylename[;key=value]. Default is null.
     */
    protected String style;

    protected FlowActor flowActor;

    /**
     * Specifies whether the cell is a vertex or edge and whether it is
     * connectable, visible and collapsed. Default values are false, false,
     * true, true and false respectively.
     */
    protected boolean vertex = false, edge = false, connectable = true,
            visible = true, collapsed = false;

    /**
     * Reference to the parent cell and source and target terminals for edges.
     */
    protected mxICell parent, source, target;

    /**
     * Holds the child cells and connected edges.
     */
    protected List<Object> children, edges;

    /**
     * Constructs a new cell with an empty user object.
     */
    public Cell() {
        this(null);
    }

    /**
     * Constructs a new cell for the given user object.
     *
     * @param value Object that represents the value of the cell.
     */
    public Cell(Object value) {
        this(value, null, null);
    }

    /**
     * Constructs a new cell for the given parameters.
     *
     * @param value    Object that represents the value of the cell.
     * @param geometry Specifies the geometry of the cell.
     * @param style    Specifies the style as a formatted string.
     */
    public Cell(Object value, mxGeometry geometry, String style) {
        setValue(value);
        setGeometry(geometry);
        setStyle(style);

        role = new CellRole(0);
        flowActor = new FlowActor();
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getId()
      */

    public String getId() {
        return id;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setId(String)
      */

    public void setId(String id) {
        this.id = id;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getValue()
      */

    public Object getValue() {
        return value;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setValue(Object)
      */

    public void setValue(Object value) {
        this.value = value;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getGeometry()
      */

    public mxGeometry getGeometry() {
        return geometry;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setGeometry(com.magnificent.graph.model.mxGeometry)
      */

    public void setGeometry(mxGeometry geometry) {
        this.geometry = geometry;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getStyle()
      */

    public String getStyle() {
        return style;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setStyle(String)
      */

    public void setStyle(String style) {
        this.style = style;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#isVertex()
      */

    public boolean isVertex() {
        return vertex;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setVertex(boolean)
      */

    public void setVertex(boolean vertex) {
        this.vertex = vertex;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#isEdge()
      */

    public boolean isEdge() {
        return edge;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setEdge(boolean)
      */

    public void setEdge(boolean edge) {
        this.edge = edge;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#isConnectable()
      */

    public boolean isConnectable() {
        return connectable;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setConnectable(boolean)
      */

    public void setConnectable(boolean connectable) {
        this.connectable = connectable;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#isVisible()
      */

    public boolean isVisible() {
        return visible;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setVisible(boolean)
      */

    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#isCollapsed()
      */

    public boolean isCollapsed() {
        return collapsed;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setCollapsed(boolean)
      */

    public void setCollapsed(boolean collapsed) {
        this.collapsed = collapsed;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getParent()
      */

    public mxICell getParent() {
        return parent;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setParent(com.magnificent.graph.model.mxICell)
      */

    public void setParent(mxICell parent) {
        this.parent = parent;
    }

    /**
     * Returns the source terminal.
     */
    public mxICell getSource() {
        return source;
    }

    /**
     * Sets the source terminal.
     *
     * @param source Cell that represents the new source terminal.
     */
    public void setSource(mxICell source) {
        this.source = source;
    }

    /**
     * Returns the target terminal.
     */
    public mxICell getTarget() {
        return target;
    }

    public void setRole(CellRole cellRole) {
        this.role = cellRole;
    }

    public CellRole getRole() {
        return role;
    }

    public FlowActor getFlowActor() {
        return flowActor;
    }

    public void setFlowActor(FlowActor flowActor) {
        this.flowActor = flowActor;
    }

    /**
     * Sets the target terminal.
     *
     * @param target Cell that represents the new target terminal.
     */
    public void setTarget(mxICell target) {
        this.target = target;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getTerminal(boolean)
      */

    public mxICell getTerminal(boolean source) {
        return (source) ? getSource() : getTarget();
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#setTerminal(com.magnificent.graph.model.mxICell, boolean)
      */

    public mxICell setTerminal(mxICell terminal, boolean isSource) {
        if (isSource) {
            setSource(terminal);
        } else {
            setTarget(terminal);
        }

        return terminal;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getChildCount()
      */

    public int getChildCount() {
        return (children != null) ? children.size() : 0;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getIndex(com.magnificent.graph.model.mxICell)
      */

    public int getIndex(mxICell child) {
        return (children != null) ? children.indexOf(child) : -1;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getChildAt(int)
      */

    public mxICell getChildAt(int index) {
        return (children != null) ? (mxICell) children.get(index) : null;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#insert(com.magnificent.graph.model.mxICell)
      */

    public mxICell insert(mxICell child) {
        return insert(child, getChildCount());
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#insert(com.magnificent.graph.model.mxICell, int)
      */

    public mxICell insert(mxICell child, int index) {
        if (child != null) {
            child.removeFromParent();
            child.setParent(this);

            if (children == null) {
                children = new ArrayList<Object>();
                children.add(child);
            } else {
                children.add(index, child);
            }
        }

        return child;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#remove(int)
      */

    public mxICell remove(int index) {
        mxICell child = null;

        if (children != null && index >= 0) {
            child = getChildAt(index);
            remove(child);
        }

        return child;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#remove(com.magnificent.graph.model.mxICell)
      */

    public mxICell remove(mxICell child) {
        if (child != null && children != null) {
            children.remove(child);
            child.setParent(null);
        }

        return child;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#removeFromParent()
      */

    public void removeFromParent() {
        if (parent != null) {
            parent.remove(this);
        }
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getEdgeCount()
      */

    public int getEdgeCount() {
        return (edges != null) ? edges.size() : 0;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getEdgeIndex(com.magnificent.graph.model.mxICell)
      */

    public int getEdgeIndex(mxICell edge) {
        return (edges != null) ? edges.indexOf(edge) : -1;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#getEdgeAt(int)
      */

    public mxICell getEdgeAt(int index) {
        return (edges != null) ? (mxICell) edges.get(index) : null;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#insertEdge(com.magnificent.graph.model.mxICell, boolean)
      */

    public mxICell insertEdge(mxICell edge, boolean isOutgoing) {
        if (edge != null) {
            edge.removeFromTerminal(isOutgoing);
            edge.setTerminal(this, isOutgoing);

            if (edges == null || edge.getTerminal(!isOutgoing) != this
                    || !edges.contains(edge)) {
                if (edges == null) {
                    edges = new ArrayList<Object>();
                }

                edges.add(edge);
            }
        }

        return edge;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#removeEdge(com.magnificent.graph.model.mxICell, boolean)
      */

    public mxICell removeEdge(mxICell edge, boolean isOutgoing) {
        if (edge != null) {
            if (edge.getTerminal(!isOutgoing) != this && edges != null) {
                edges.remove(edge);
            }

            edge.setTerminal(null, isOutgoing);
        }

        return edge;
    }

    /* (non-Javadoc)
      * @see com.magnificent.graph.model.mxICell#removeFromTerminal(boolean)
      */

    public void removeFromTerminal(boolean isSource) {
        mxICell terminal = getTerminal(isSource);

        if (terminal != null) {
            terminal.removeEdge(this, isSource);
        }
    }

    /**
     * Returns the specified attribute from the user object if it is an XML
     * node.
     *
     * @param name Name of the attribute whose value should be returned.
     * @return Returns the value of the given attribute or null.
     */
    public String getAttribute(String name) {
        return getAttribute(name, null);
    }

    /**
     * Returns the specified attribute from the user object if it is an XML
     * node.
     *
     * @param name         Name of the attribute whose value should be returned.
     * @param defaultValue Default value to use if the attribute has no value.
     * @return Returns the value of the given attribute or defaultValue.
     */
    public String getAttribute(String name, String defaultValue) {
        Object userObject = getValue();
        String val = null;

        if (userObject instanceof Element) {
            Element element = (Element) userObject;
            val = element.getAttribute(name);
        }

        if (val == null) {
            val = defaultValue;
        }

        return val;
    }

    /**
     * Sets the specified attribute on the user object if it is an XML node.
     *
     * @param name  Name of the attribute whose value should be set.
     * @param value New value of the attribute.
     */
    public void setAttribute(String name, String value) {
        Object userObject = getValue();

        if (userObject instanceof Element) {
            Element element = (Element) userObject;
            element.setAttribute(name, value);
        }
    }

    /**
     * Returns a clone of the cell.
     */
    public Object clone() throws CloneNotSupportedException {
        Cell clone = (Cell) super.clone();

        clone.setValue(cloneValue());
        clone.setStyle(getStyle());
        clone.setCollapsed(isCollapsed());
        clone.setConnectable(isConnectable());
        clone.setEdge(isEdge());
        clone.setVertex(isVertex());
        clone.setVisible(isVisible());
        clone.setParent(null);
        clone.setSource(null);
        clone.setTarget(null);
        clone.children = null;
        clone.edges = null;

        mxGeometry geometry = getGeometry();

        if (geometry != null) {
            clone.setGeometry((mxGeometry) geometry.clone());
        }

        return clone;
    }

    /**
     * Returns a clone of the user object. This implementation clones any XML
     * nodes or otherwise returns the same user object instance.
     */
    protected Object cloneValue() {
        Object value = getValue();

        if (value instanceof Node) {
            value = ((Node) value).cloneNode(true);
        }

        return value;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();

        builder.append("{\n");

        builder.append("\t[Id=").append(id).append("]\n");
        builder.append("\t[Value=").append(value).append("]\n");
        builder.append("\t[Style=").append(style).append("]\n");

        builder.append("}\n");

        return builder.toString();
    }
}

  /* converted to utf8 */